//---------------------------------------------------------------------------
// <copyright file="RibbonTabHeadersPanel.cs" company="Microsoft Corporation">
//     Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//---------------------------------------------------------------------------

namespace Microsoft.Windows.Controls.Ribbon.Primitives
{
	using MS.Internal;
	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Windows;
	using System.Windows.Controls;
	using System.Windows.Controls.Primitives;
	using System.Windows.Media;

	/// <summary>
	///     The items panel for RibbonTabHeaderItemsControl
	/// </summary>
	public class RibbonTabHeadersPanel : Panel, IScrollInfo
	{
		#region Protected Methods

		/// <summary>
		///     Measure
		/// </summary>
		protected override Size MeasureOverride(Size availableSize)
		{
			// Note: The logic below assumes that the TabHeaders belonging to inactive
			// ContextualTabGroups have their IsVisible set to false.

			//_separatorOpacity = 0.0;
			Size desiredSize = new Size();

			UIElementCollection children = InternalChildren;
			if (children.Count == 0)
			{
				return desiredSize;
			}

			Size childConstraint = new Size(double.PositiveInfinity, availableSize.Height);
			int countRegularTabs = 0;
			double totalDefaultPaddingAllTabHeaders = 0;
			double totalDefaultPaddingRegularTabHeaders = 0;
			double totalDesiredWidthRegularTabHeaders = 0;
			bool showRegularTabHeaderToolTips = false;
			bool showContextualTabHeaderToolTips = false;
			int countVisibleTabs = 0;

			// Measure all TabHeaders to fit their content
			// desiredSize should hold the total size required to fit all TabHeaders
			desiredSize = InitialMeasure(childConstraint,
										 out totalDefaultPaddingAllTabHeaders,
										 out totalDefaultPaddingRegularTabHeaders,
										 out totalDesiredWidthRegularTabHeaders,
										 out countRegularTabs,
										 out countVisibleTabs);

			int countContextualTabs = countVisibleTabs - countRegularTabs;
			SpaceAvailable = 0.0;
			double overflowWidth = desiredSize.Width - availableSize.Width; // Total overflow width
			if (DoubleUtil.GreaterThan(overflowWidth, 0))
			{
				// Calculate max tab width if tab clipping is necessary
				double totalClipWidthRegularTabHeaders = 0; // How much pixels we need to clip regular tabs
				double totalClipWidthContextualTabHeaders = 0; // How much pixels we need to clip contextual tabs

				if (DoubleUtil.GreaterThan(overflowWidth, totalDefaultPaddingAllTabHeaders)) // Clipping is necessary - all tabs padding will we 0
				{
					showRegularTabHeaderToolTips = true;
					totalClipWidthRegularTabHeaders = overflowWidth - totalDefaultPaddingAllTabHeaders; // Try to use the whole totalClipAmount in the regular tabs
				}

				double maxRegularTabHeaderWidth = CalculateMaxTabHeaderWidth(totalClipWidthRegularTabHeaders, false);
				if (DoubleUtil.AreClose(maxRegularTabHeaderWidth, _tabHeaderMinWidth)) // Regular tabs are clipped to the min size -  need to clip contextual tabs
				{
					showContextualTabHeaderToolTips = true;
					double totalClipAmount = overflowWidth - totalDefaultPaddingAllTabHeaders;
					double usedClipAmount = totalDesiredWidthRegularTabHeaders - totalDefaultPaddingRegularTabHeaders - (_tabHeaderMinWidth * countRegularTabs);
					totalClipWidthContextualTabHeaders = totalClipAmount - usedClipAmount; // Remaining clipping amount
				}

				double maxContextualTabHeaderWidth = CalculateMaxTabHeaderWidth(totalClipWidthContextualTabHeaders, true);
				double reducePaddingRegularTabHeader = 0;
				double reducePaddingContextualTabHeader = 0;

				if (DoubleUtil.GreaterThanOrClose(totalDefaultPaddingRegularTabHeaders, overflowWidth))
				{
					reducePaddingRegularTabHeader = (0.5 * overflowWidth) / countRegularTabs;
					//_separatorOpacity = Math.Max(0.0, reducePaddingRegularTabHeader * 0.2);
				}
				else
				{
					//_separatorOpacity = 1.0;
					reducePaddingRegularTabHeader = double.PositiveInfinity;
					// If countContextualTabs==0 then reducePaddingContextualTab will become Infinity
					reducePaddingContextualTabHeader = (0.5 * (overflowWidth - totalDefaultPaddingRegularTabHeaders)) / (countVisibleTabs - countRegularTabs);
				}

				desiredSize = FinalMeasure(childConstraint,
										   reducePaddingContextualTabHeader,
										   reducePaddingRegularTabHeader,
										   maxContextualTabHeaderWidth,
										   maxRegularTabHeaderWidth);
			}
			else if (countContextualTabs > 0)
			{
				// After assigning DefaultPadding, we are left with extra space. 
				// If contextual tabs need that extra space, assign them more padding. 
				NotifyDesiredWidthChanged();
				double spaceAvailable = availableSize.Width - desiredSize.Width;
				double availableExtraWidthPerTab = CalculateMaxPadding(spaceAvailable);

				foreach (UIElement child in InternalChildren)
				{
					RibbonTabHeader ribbonTabHeader = child as RibbonTabHeader;
					if (ribbonTabHeader != null && ribbonTabHeader.IsVisible && ribbonTabHeader.IsContextualTab)
					{
						RibbonContextualTabGroup ctg = ribbonTabHeader.ContextualTabGroup;
						if (ctg != null && DoubleUtil.GreaterThan(ctg.DesiredExtraPaddingPerTab, 0.0))
						{
							double desiredExtraPaddingPerTab = ctg.DesiredExtraPaddingPerTab;
							double availableExtraWidth = Math.Min(desiredExtraPaddingPerTab, availableExtraWidthPerTab);

							Thickness newPadding = ribbonTabHeader.Padding;
							newPadding.Left += availableExtraWidth * 0.5;
							newPadding.Right += availableExtraWidth * 0.5;
							ribbonTabHeader.Padding = newPadding;

							// Remeasure with added padding
							desiredSize.Width -= ribbonTabHeader.DesiredSize.Width;
							ribbonTabHeader.Measure(new Size(Double.MaxValue, childConstraint.Height));
							desiredSize.Width += ribbonTabHeader.DesiredSize.Width;
						}
					}
				}
			}

			// If the difference between desiredWidth and constraintWidth is less
			// than 1e-10 then assume that both are same. This avoids unnecessary
			// inequalities in extent and viewport resulting in spontaneous
			// flickering of scroll button.
			if (Math.Abs(desiredSize.Width - availableSize.Width) < _desiredWidthEpsilon)
			{
				desiredSize.Width = availableSize.Width;
			}

			SpaceAvailable = availableSize.Width - desiredSize.Width;
			// Update ContextualTabGroup.TabsDesiredWidth
			NotifyDesiredWidthChanged();

			// Update whether tooltips should be shown.
			UpdateToolTips(showRegularTabHeaderToolTips, showContextualTabHeaderToolTips);

			VerifyScrollData(availableSize.Width, desiredSize.Width);


			// Invalidate ContextualTabHeadersPanel
			if (Ribbon != null)
			{
				RibbonContextualTabGroupItemsControl groupHeaderItemsControl = Ribbon.ContextualTabGroupItemsControl;
				if (groupHeaderItemsControl != null && groupHeaderItemsControl.InternalItemsHost != null)
				{
					groupHeaderItemsControl.InternalItemsHost.InvalidateMeasure();
				}
			}

			return desiredSize;
		}

		/// <summary>
		///     Arrange
		/// </summary>
		protected override Size ArrangeOverride(Size finalSize)
		{
			// Note: The logic below assumes that the TabHeaders belonging to inactive
			// ContextualTabGroups have their IsVisible set to false.

			// 14 November 2013
			// Ming Slogar
			// CA1804: Remove unused locals
			//
			//UIElementCollection children = InternalChildren;

			// 14 October 2013
			// Ming Slogar
			// CA1804: Remove unused locals
			//
			// int childCount = children.Count;
			double childX = 0.0;
			Dictionary<object, List<RibbonTabHeaderAndIndex>> contextualTabHeaders = new Dictionary<object, List<RibbonTabHeaderAndIndex>>();
			Ribbon ribbon = Ribbon;
			if (ribbon != null)
			{
				ribbon.TabDisplayIndexToIndexMap.Clear();
				ribbon.TabIndexToDisplayIndexMap.Clear();
			}
			int displayIndex = 0;
			ArrangeRegularTabHeaders(finalSize,
							   ribbon,
							   contextualTabHeaders,
							   ref displayIndex,
							   ref childX);

			ArrangeContextualTabHeaders(finalSize,
								  ribbon,
								  contextualTabHeaders,
								  ref displayIndex,
								  ref childX);


			// this arrange happens after the RibbonTitlePanel arrange 
			// so we need to update the RibbonContextualTabGroup positions whenever RibbonTabHeaders move around
			if (Ribbon != null)
			{
				// Invalidate TitlePanel
				if (Ribbon.RibbonTitlePanel != null)
				{
					Ribbon.RibbonTitlePanel.InvalidateMeasure();
					Ribbon.RibbonTitlePanel.InvalidateArrange();
				}

				// Invalidate ContextualTabHeadersPanel
				RibbonContextualTabGroupItemsControl groupHeaderItemsControl = Ribbon.ContextualTabGroupItemsControl;
				if (groupHeaderItemsControl != null && groupHeaderItemsControl.InternalItemsHost != null)
				{
					groupHeaderItemsControl.InternalItemsHost.InvalidateArrange();
				}
			}

			//InvalidateVisual();

			return finalSize;
		}

		/// <summary>
		///     Draw the separators if needed.
		/// </summary>
		/// <param name="drawingContext"></param>
		protected override void OnRender(DrawingContext drawingContext)
		{
			//UIElementCollection children = InternalChildren;
			//int count = children.Count;
			//if (!SystemParameters.HighContrast && DoubleUtil.GreaterThan(_separatorOpacity, 0))
			//{
			//	Ribbon ribbon = Ribbon;
			//	Pen separatorPen = SeparatorPen;
			//	if (ribbon != null && separatorPen != null)
			//	{
			//		double xOffset = -HorizontalOffset;
			//		separatorPen.Brush.Opacity = _separatorOpacity;

			//		int elementCount = ribbon.TabDisplayIndexToIndexMap.Count;
			//		for (int i = 0; i < elementCount; i++)
			//		{
			//			Debug.Assert(ribbon.TabDisplayIndexToIndexMap.ContainsKey(i));
			//			int index = ribbon.TabDisplayIndexToIndexMap[i];
			//			Debug.Assert(children.Count > index && index >= 0);
			//			UIElement child = children[index];
			//			if (!child.IsVisible)
			//			{
			//				continue;
			//			}
			//			xOffset += child.DesiredSize.Width;
			//			drawingContext.DrawLine(separatorPen, new Point(xOffset, 0), new Point(xOffset, this.ActualHeight));
			//		}
			//	}
			//}

			base.OnRender(drawingContext);
		}

		/// <summary>
		///     This method is invoked when the IsItemsHost property changes.
		/// </summary>
		/// <param name="oldIsItemsHost">The old value of the IsItemsHost property.</param>
		/// <param name="newIsItemsHost">The new value of the IsItemsHost property.</param>
		protected override void OnIsItemsHostChanged(bool oldIsItemsHost, bool newIsItemsHost)
		{
			base.OnIsItemsHostChanged(oldIsItemsHost, newIsItemsHost);

			if (newIsItemsHost)
			{
				RibbonTabHeaderItemsControl tabHeaderItemsControl = ParentTabHeaderItemsControl;
				if (tabHeaderItemsControl != null)
				{
					IItemContainerGenerator generator = tabHeaderItemsControl.ItemContainerGenerator as IItemContainerGenerator;
					if (generator != null && generator.GetItemContainerGeneratorForPanel(this) == generator)
					{
						tabHeaderItemsControl.InternalItemsHost = this;
					}
				}
			}
			else
			{
				RibbonTabHeaderItemsControl tabHeaderItemsControl = ParentTabHeaderItemsControl;
				if (tabHeaderItemsControl != null && tabHeaderItemsControl.InternalItemsHost == this)
				{
					tabHeaderItemsControl.InternalItemsHost = null;
				}
			}
		}

		#endregion

		#region Properties

		/// <summary>
		///     The parent RibbonTabHeaderItemsControl
		/// </summary>
		private RibbonTabHeaderItemsControl ParentTabHeaderItemsControl
		{
			get
			{
				FrameworkElement itemsPresenter = TemplatedParent as FrameworkElement;
				if (itemsPresenter != null)
				{
					return itemsPresenter.TemplatedParent as RibbonTabHeaderItemsControl;
				}

				return null;
			}
		}

		/// <summary>
		///     DependencyProperty for Ribbon property.
		/// </summary>
		public static readonly DependencyProperty RibbonProperty =
			RibbonControlService.RibbonProperty.AddOwner(typeof(RibbonTabHeadersPanel));

		/// <summary>
		///     This property is used to access Ribbon
		/// </summary>
		public Ribbon Ribbon
		{
			get { return RibbonControlService.GetRibbon(this); }
		}

		private Pen SeparatorPen
		{
			get
			{
				if (_separatorPen == null)
				{
					Ribbon ribbon = Ribbon;
					if (ribbon != null && ribbon.BorderBrush != null)
					{
						Brush b = ribbon.BorderBrush.Clone();
						_separatorPen = new Pen(b, 1.0);
					}
				}

				return _separatorPen;
			}
		}

		#endregion

		#region Internal Methods

		internal void OnNotifyRibbonBorderBrushChanged()
		{
			_separatorPen = null;
			InvalidateVisual();
		}

		#endregion

		#region Private Methods

		/// <summary>
		///     Measures all the children with original constraints.
		/// </summary>
		private Size InitialMeasure(Size constraint,
			out double totalDefaultPaddingAllTabHeaders,
			out double totalDefaultPaddingRegularTabHeaders,
			out double totalDesiredWidthRegularTabHeaders,
			out int countRegularTabs,
			out int countVisibleTabs)
		{
			totalDefaultPaddingAllTabHeaders = 0;
			totalDefaultPaddingRegularTabHeaders = 0;
			totalDesiredWidthRegularTabHeaders = 0;
			countRegularTabs = 0;
			countVisibleTabs = 0;

			UIElementCollection children = InternalChildren;
			Size desiredSize = new Size();
			int countAllTabs = children.Count;
			for (int i = 0; i < countAllTabs; i++)
			{
				RibbonTabHeader ribbonTabHeader = children[i] as RibbonTabHeader;
				if (ribbonTabHeader != null)
				{
					if (!ribbonTabHeader.IsVisible)
					{
						continue;
					}
					ribbonTabHeader.Padding = ribbonTabHeader.DefaultPadding; // Always do first meassure with default padding
					double tabHeaderPadding = ribbonTabHeader.DefaultPadding.Left + ribbonTabHeader.DefaultPadding.Right;
					totalDefaultPaddingAllTabHeaders += tabHeaderPadding;
					bool isContextualTab = ribbonTabHeader.IsContextualTab;
					ribbonTabHeader.Measure(constraint);
					desiredSize.Width += ribbonTabHeader.DesiredSize.Width;
					desiredSize.Height = Math.Max(desiredSize.Height, ribbonTabHeader.DesiredSize.Height);
					if (!isContextualTab)
					{
						totalDefaultPaddingRegularTabHeaders += tabHeaderPadding;
						totalDesiredWidthRegularTabHeaders += ribbonTabHeader.DesiredSize.Width;
						countRegularTabs++;
					}
				}
				else
				{
					UIElement child = children[i];
					if (!child.IsVisible)
					{
						continue;
					}
					child.Measure(constraint);
					desiredSize.Width += child.DesiredSize.Width;
					desiredSize.Height = Math.Max(desiredSize.Height, child.DesiredSize.Height);
					totalDesiredWidthRegularTabHeaders += child.DesiredSize.Width;
					countRegularTabs++;
				}
				countVisibleTabs++;
			}
			return desiredSize;
		}

		/// <summary>
		///     Measures all the children with final constraints
		/// </summary>
		private Size FinalMeasure(Size constraint,
			double reducePaddingContextualTabHeader,
			double reducePaddingRegularTabHeader,
			double maxContextualTabHeaderWidth,
			double maxRegularTabHeaderWidth)
		{
			Size desiredSize = new Size();
			UIElementCollection children = InternalChildren;
			int countAllTabs = children.Count;
			for (int i = 0; i < countAllTabs; i++)
			{
				RibbonTabHeader ribbonTabHeader = children[i] as RibbonTabHeader;
				if (ribbonTabHeader != null)
				{
					if (!ribbonTabHeader.IsVisible)
					{
						continue;
					}
					bool isContextualTab = ribbonTabHeader.IsContextualTab;
					double leftPadding = Math.Max(0, ribbonTabHeader.DefaultPadding.Left - (isContextualTab ? reducePaddingContextualTabHeader : reducePaddingRegularTabHeader));
					double rightPadding = Math.Max(0, ribbonTabHeader.DefaultPadding.Right - (isContextualTab ? reducePaddingContextualTabHeader : reducePaddingRegularTabHeader));
					ribbonTabHeader.Padding = new Thickness(leftPadding, ribbonTabHeader.DefaultPadding.Top, rightPadding, ribbonTabHeader.DefaultPadding.Bottom);

					ribbonTabHeader.Measure(new Size(isContextualTab ? maxContextualTabHeaderWidth : maxRegularTabHeaderWidth, constraint.Height));

					desiredSize.Width += ribbonTabHeader.DesiredSize.Width;
					desiredSize.Height = Math.Max(desiredSize.Height, ribbonTabHeader.DesiredSize.Height);
				}
				else
				{
					UIElement child = children[i];
					if (!child.IsVisible)
					{
						continue;
					}
					child.Measure(new Size(maxRegularTabHeaderWidth, constraint.Height));
					desiredSize.Width += child.DesiredSize.Width;
					desiredSize.Height = Math.Max(desiredSize.Height, child.DesiredSize.Height);
				}
			}

			return desiredSize;
		}

		// This method determine how much tabs will be clipped by caluclating the maximum tab width
		// clipWidth parameter is the amount the needs to be removed from tabs
		// Algorithm steps:
		// 1. Sort all tabs sizes
		// 2. maxTabWidth = max tab size - clipWidth
		// 3. if there is an element bigger that maxTabWidth - include this element and calulate new average
		// 4. Return maxTabWidth coerced with some min width
		private double CalculateMaxTabHeaderWidth(double clipWidth, bool forContextualTabs)
		{
			// If clipping is not necessary - return Max double
			if (DoubleUtil.LessThanOrClose(clipWidth, 0))
			{
				return Double.MaxValue;
			}

			UIElementCollection children = InternalChildren;

			// 14 October 2013
			// Ming Slogar
			// CA1804: Remove unused locals
			//	
			// int childCount = children.Count;

			// Sort element sizes without the padding
			List<double> elementSizes = new List<double>();
			foreach (UIElement element in children)
			{
				if (!element.IsVisible)
				{
					continue;
				}
				double elementSize = element.DesiredSize.Width;
				RibbonTabHeader tabHeader = element as RibbonTabHeader;
				if (tabHeader != null)
				{
					if (tabHeader.IsContextualTab != forContextualTabs)
					{
						continue;
					}
					elementSize = elementSize - tabHeader.DefaultPadding.Left - tabHeader.DefaultPadding.Right;
				}
				elementSizes.Add(elementSize);
			}
			int sizeCount = elementSizes.Count;
			if (sizeCount == 0)
			{
				return _tabHeaderMinWidth;
			}
			elementSizes.Sort();


			// Clip the max element
			double maxTabHeaderWidth = elementSizes[sizeCount - 1] - clipWidth;
			for (int i = 1; i < sizeCount; i++)
			{
				double currentWidth = elementSizes[sizeCount - 1 - i];
				if (DoubleUtil.GreaterThanOrClose(maxTabHeaderWidth, currentWidth))
				{
					break;
				}
				// Include next element and calculate new average
				maxTabHeaderWidth = ((maxTabHeaderWidth * i) + currentWidth) / (i + 1);
			}
			return Math.Max(_tabHeaderMinWidth, maxTabHeaderWidth);
		}

		/// <summary>
		/// This algorithm calculates the extra Padding that can be assigned to a contextual tab. 
		/// </summary>
		/// <param name="spaceAvailable"></param>
		/// <returns></returns>
		private double CalculateMaxPadding(double spaceAvailable)
		{
			UIElementCollection children = InternalChildren;

			// 14 October 2013
			// Ming Slogar
			// CA1804: Remove unused locals
			//
			// int childCount = children.Count;

			// Sort DesiredPaddings
			List<double> desiredPaddings = new List<double>();
			double totalDesiredPadding = 0.0;
			foreach (UIElement element in children)
			{
				if (!element.IsVisible)
				{
					continue;
				}
				RibbonTabHeader tabHeader = element as RibbonTabHeader;
				if (tabHeader != null && tabHeader.IsContextualTab)
				{
					RibbonContextualTabGroup tabGroup = tabHeader.ContextualTabGroup;
					if (tabGroup != null && DoubleUtil.GreaterThan(tabGroup.DesiredExtraPaddingPerTab, 0.0))
					{
						// ContextualTabGroup requires this much more width to reach its ideal DesiredSize. 
						double desiredPaddingPerTabHeader = tabGroup.DesiredExtraPaddingPerTab;
						desiredPaddings.Add(desiredPaddingPerTabHeader);
						totalDesiredPadding += desiredPaddingPerTabHeader;
					}
				}
			}
			int sizeCount = desiredPaddings.Count;
			if (sizeCount == 0)
			{
				return 0.0;
			}
			desiredPaddings.Sort();

			double delta = totalDesiredPadding - spaceAvailable;
			if (DoubleUtil.LessThanOrClose(delta, 0.0))
			{
				return desiredPaddings[sizeCount - 1];
			}

			// Clip the TabHeader requesting most extra Padding
			double maxDesiredPadding = desiredPaddings[sizeCount - 1] - delta;
			for (int i = 1; i < sizeCount; i++)
			{
				double currentDesiredPadding = desiredPaddings[sizeCount - 1 - i];
				if (DoubleUtil.GreaterThanOrClose(maxDesiredPadding, currentDesiredPadding))
				{
					break;
				}
				// Include next element and calculate new average
				maxDesiredPadding = ((maxDesiredPadding * i) + currentDesiredPadding) / (i + 1);
			}
			return maxDesiredPadding;
		}

		/// <summary>
		/// Called whenever RibbonTabHeaders are measured
		/// Sums up DesiredSize.Width of each RibbonTabHeader belonging to a ContextualTabGroup and stores it as ContextualTabGroup.TabsDesiredWidth.
		/// </summary>
		private void NotifyDesiredWidthChanged()
		{
			// Invalidate ContextualTabHeadersPanel
			RibbonContextualTabGroupItemsControl groupHeaderItemsControl = Ribbon.ContextualTabGroupItemsControl;
			if (groupHeaderItemsControl != null && groupHeaderItemsControl.InternalItemsHost != null)
			{
				foreach (RibbonContextualTabGroup tabGroup in groupHeaderItemsControl.InternalItemsHost.Children)
				{
					tabGroup.TabsDesiredWidth = 0.0;
					tabGroup.DesiredExtraPaddingPerTab = 0.0;
				}
			}

			foreach (UIElement element in InternalChildren)
			{
				RibbonTabHeader tabHeader = element as RibbonTabHeader;
				if (tabHeader != null && tabHeader.IsVisible && tabHeader.IsContextualTab)
				{
					RibbonContextualTabGroup tabGroup = tabHeader.ContextualTabGroup;
					if (tabGroup != null)
					{
						double previousTabCount = 0;
						if (!DoubleUtil.IsZero(tabGroup.DesiredExtraPaddingPerTab))
						{
							previousTabCount = (tabGroup.IdealDesiredWidth - tabGroup.TabsDesiredWidth) / tabGroup.DesiredExtraPaddingPerTab;
						}
						tabGroup.TabsDesiredWidth += tabHeader.DesiredSize.Width;
						// compute new average                        
						tabGroup.DesiredExtraPaddingPerTab = (tabGroup.IdealDesiredWidth - tabGroup.TabsDesiredWidth) / (previousTabCount + 1);
					}
				}
			}
		}

		/// <summary>
		///     Set show tooltips depending on whether the tab header is clipped or not.
		/// </summary>
		/// <param name="showRegularTabHeaderToolTips"></param>
		/// <param name="showContextualTabHeaderToolTips"></param>
		private void UpdateToolTips(bool showRegularTabHeaderToolTips, bool showContextualTabHeaderToolTips)
		{
			UIElementCollection children = InternalChildren;
			int countAllTabs = children.Count;
			for (int i = 0; i < countAllTabs; i++)
			{
				RibbonTabHeader tabHeader = children[i] as RibbonTabHeader;
				if (tabHeader != null)
				{
					tabHeader.ShowLabelToolTip = tabHeader.IsContextualTab ? showContextualTabHeaderToolTips : showRegularTabHeaderToolTips;
				}
			}
		}

		private struct RibbonTabHeaderAndIndex
		{
			public RibbonTabHeader RibbonTabHeader { get; set; }
			public int Index { get; set; }
		}

		/// <summary>
		///     Arranges regular tab headers and builds a map of
		///     RibbonTab.ContextualTabGroupHeader to list of RibbonTabHeaders
		/// </summary>
		/// <param name="arrangeSize"></param>
		/// <param name="ribbon"></param>
		/// <param name="contextualTabHeaders"></param>
		/// <param name="displayIndex"></param>
		/// <param name="childX"></param>
		private void ArrangeRegularTabHeaders(Size arrangeSize,
			Ribbon ribbon,
			Dictionary<object, List<RibbonTabHeaderAndIndex>> contextualTabHeaders,
			ref int displayIndex,
			ref double childX)
		{
			UIElementCollection children = InternalChildren;
			int childCount = children.Count;
			for (int i = 0; i < childCount; i++)
			{
				UIElement child = children[i];
				if (!child.IsVisible)
				{
					continue;
				}
				RibbonTabHeader tabHeader = child as RibbonTabHeader;
				if (tabHeader != null)
				{
					RibbonTab tab = tabHeader.RibbonTab;
					if (tab != null && tab.IsContextualTab)
					{
						object contextualTabGroupHeader = tab.ContextualTabGroupHeader;
						if (!contextualTabHeaders.ContainsKey(contextualTabGroupHeader))
						{
							contextualTabHeaders[contextualTabGroupHeader] = new List<RibbonTabHeaderAndIndex>();
						}
						contextualTabHeaders[contextualTabGroupHeader].Add(new RibbonTabHeaderAndIndex() { RibbonTabHeader = tabHeader, Index = i });
						continue;
					}
				}

				child.InvalidateVisual();
				child.Arrange(new Rect(childX - HorizontalOffset, arrangeSize.Height - child.DesiredSize.Height, child.DesiredSize.Width, child.DesiredSize.Height));
				childX += child.DesiredSize.Width;
				if (ribbon != null)
				{
					ribbon.TabDisplayIndexToIndexMap[displayIndex] = i;
					ribbon.TabIndexToDisplayIndexMap[i] = displayIndex;
					displayIndex++;
				}
			}
		}

		/// <summary>
		///     Arranges contextual tab headers
		/// </summary>
		private void ArrangeContextualTabHeaders(Size arrangeSize,
			Ribbon ribbon,
			Dictionary<object, List<RibbonTabHeaderAndIndex>> contextualTabHeaders,
			ref int displayIndex,
			ref double childX)
		{
			if (ribbon != null)
			{
				RibbonContextualTabGroupItemsControl groupHeaderItemsControl = ribbon.ContextualTabGroupItemsControl;
				if (groupHeaderItemsControl != null)
				{
					if (groupHeaderItemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
					{
						int groupHeaderCount = groupHeaderItemsControl.Items.Count;
						for (int i = 0; i < groupHeaderCount; i++)
						{
							RibbonContextualTabGroup groupHeader = groupHeaderItemsControl.ItemContainerGenerator.ContainerFromIndex(i) as RibbonContextualTabGroup;
							if (groupHeader != null)
							{
								object contextualTabGroupHeader = groupHeader.Header;
								if (contextualTabGroupHeader != null && contextualTabHeaders.ContainsKey(contextualTabGroupHeader))
								{
									foreach (RibbonTabHeaderAndIndex headerAndIndex in contextualTabHeaders[contextualTabGroupHeader])
									{
										RibbonTabHeader child = headerAndIndex.RibbonTabHeader;
										Debug.Assert(child != null);
										child.InvalidateVisual();
										child.Arrange(new Rect(childX - HorizontalOffset, arrangeSize.Height - child.DesiredSize.Height, child.DesiredSize.Width, child.DesiredSize.Height));
										childX += child.DesiredSize.Width;

										ribbon.TabDisplayIndexToIndexMap[displayIndex] = headerAndIndex.Index;
										ribbon.TabIndexToDisplayIndexMap[headerAndIndex.Index] = displayIndex;
										displayIndex++;
									}
								}
							}
						}

						RibbonContextualTabGroupsPanel contextualTabGroupsPanel = groupHeaderItemsControl.InternalItemsHost as RibbonContextualTabGroupsPanel;
						if (contextualTabGroupsPanel != null)
						{
							contextualTabGroupsPanel.WaitingForMeasure = false;
						}
					}
					else
					{
						// Tell the ContextualTabGroupsPanel that we are waiting on its Measure.
						RibbonContextualTabGroupsPanel contextualTabGroupsPanel = groupHeaderItemsControl.InternalItemsHost as RibbonContextualTabGroupsPanel;
						if (contextualTabGroupsPanel != null)
						{
							contextualTabGroupsPanel.WaitingForMeasure = true;
						}
					}
				}
			}
		}

		#endregion

		/// <summary>
		/// Gets or sets how much width is available in this Panel. 
		/// </summary>
		internal double SpaceAvailable
		{
			get;
			set;
		}

		#region Private Data

		// 14 October 2013
		// Ming Slogar
		// CA1823: Avoid unused private fields
		//
		//private double _separatorOpacity;
		private const double _tabHeaderMinWidth = 30.0;
		private const double _desiredWidthEpsilon = 1e-10;
		Pen _separatorPen = null;

		#endregion

		#region IScrollInfo
		// Verifies scrolling data using the passed viewport and extent as newly computed values.
		// Checks the X/Y offset and coerces them into the range [0, Extent - ViewportSize]
		// If extent, viewport, or the newly coerced offsets are different than the existing offset,
		//   cachces are updated and InvalidateScrollInfo() is called.
		private void VerifyScrollData(double viewportWidth, double extentWidth)
		{
			bool fValid = true;

			if (Double.IsInfinity(viewportWidth))
			{
				viewportWidth = extentWidth;
			}

			double offsetX = CoerceOffset(ScrollData._offsetX, extentWidth, viewportWidth);

			fValid &= DoubleUtil.AreClose(viewportWidth, ScrollData._viewportWidth);
			fValid &= DoubleUtil.AreClose(extentWidth, ScrollData._extentWidth);
			fValid &= DoubleUtil.AreClose(ScrollData._offsetX, offsetX);

			ScrollData._viewportWidth = viewportWidth;
			ScrollData._extentWidth = extentWidth;
			ScrollData._offsetX = offsetX;

			if (!fValid)
			{
				if (ScrollOwner != null)
				{
					ScrollOwner.InvalidateScrollInfo();
				}
			}
		}

		// Returns an offset coerced into the [0, Extent - Viewport] range.
		// Internal because it is also used by other Avalon ISI implementations (just to avoid code duplication).
		internal static double CoerceOffset(double offset, double extent, double viewport)
		{
			if (DoubleUtil.GreaterThan(offset, extent - viewport))
			{
				offset = extent - viewport;
			}

			if (DoubleUtil.LessThan(offset, 0))
			{
				offset = 0;
			}

			return offset;
		}

		public ScrollViewer ScrollOwner
		{
			get { return ScrollData._scrollOwner; }
			set
			{
				if (ScrollData._scrollOwner != value)
				{
					if (Ribbon != null)
					{
						Ribbon.NotifyTabHeadersScrollOwnerChanged(ScrollData._scrollOwner, value);
					}
					ScrollData._scrollOwner = value;
				}
			}
		}

		public void SetHorizontalOffset(double offset)
		{
			double newValue = ValidateInputOffset(offset, "HorizontalOffset");
			if (!DoubleUtil.AreClose(ScrollData._offsetX, newValue))
			{
				_scrollData._offsetX = newValue;
				InvalidateMeasure();
			}
		}

		public double ExtentWidth
		{
			get { return ScrollData._extentWidth; }
		}

		public double HorizontalOffset
		{
			get { return ScrollData._offsetX; }
		}

		public double ViewportWidth
		{
			get { return ScrollData._viewportWidth; }
		}

		public void LineLeft()
		{
			SetHorizontalOffset(HorizontalOffset - 16.0);
		}

		public void LineRight()
		{
			SetHorizontalOffset(HorizontalOffset + 16.0);
		}

		// This is optimized for horizontal scrolling only
		public Rect MakeVisible(Visual visual, Rect rectangle)
		{
			// We can only work on visuals that are us or children.
			// An empty rect has no size or position.  We can't meaningfully use it.
			if (rectangle.IsEmpty
				|| visual == null
				|| visual == (Visual)this
				|| !this.IsAncestorOf(visual))
			{
				return Rect.Empty;
			}

			// Compute the child's rect relative to (0,0) in our coordinate space.
			GeneralTransform childTransform = visual.TransformToAncestor(this);

			rectangle = childTransform.TransformBounds(rectangle);

			// Initialize the viewport
			Rect viewport = new Rect(HorizontalOffset, rectangle.Top, ViewportWidth, rectangle.Height);
			rectangle.X += viewport.X;

			// Compute the offsets required to minimally scroll the child maximally into view.
			double minX = ComputeScrollOffsetWithMinimalScroll(viewport.Left, viewport.Right, rectangle.Left, rectangle.Right);

			// We have computed the scrolling offsets; scroll to them.
			double originalOffset = ScrollData._offsetX;
			SetHorizontalOffset(minX);

			if (!DoubleUtil.AreClose(originalOffset, ScrollData._offsetX))
			{
				OnScrollChange();
			}

			// Compute the visible rectangle of the child relative to the viewport.
			viewport.X = minX;
			rectangle.Intersect(viewport);

			rectangle.X -= viewport.X;

			// Return the rectangle
			return rectangle;
		}

		private void OnScrollChange()
		{
			if (ScrollOwner != null) { ScrollOwner.InvalidateScrollInfo(); }
		}

		internal static double ComputeScrollOffsetWithMinimalScroll(
			double topView,
			double bottomView,
			double topChild,
			double bottomChild)
		{
			// # CHILD POSITION       CHILD SIZE      SCROLL      REMEDY
			// 1 Above viewport       <= viewport     Down        Align top edge of child & viewport
			// 2 Above viewport       > viewport      Down        Align bottom edge of child & viewport
			// 3 Below viewport       <= viewport     Up          Align bottom edge of child & viewport
			// 4 Below viewport       > viewport      Up          Align top edge of child & viewport
			// 5 Entirely within viewport             NA          No scroll.
			// 6 Spanning viewport                    NA          No scroll.
			//
			// Note: "Above viewport" = childTop above viewportTop, childBottom above viewportBottom
			//       "Below viewport" = childTop below viewportTop, childBottom below viewportBottom
			// These child thus may overlap with the viewport, but will scroll the same direction
			bool fAbove = DoubleUtil.LessThan(topChild, topView) && DoubleUtil.LessThan(bottomChild, bottomView);
			bool fBelow = DoubleUtil.GreaterThan(bottomChild, bottomView) && DoubleUtil.GreaterThan(topChild, topView);
			bool fLarger = (bottomChild - topChild) > (bottomView - topView);

			// Handle Cases:  1 & 4 above
			if ((fAbove && !fLarger)
			   || (fBelow && fLarger))
			{
				return topChild;
			}

			// Handle Cases: 2 & 3 above
			else if (fAbove || fBelow)
			{
				return bottomChild - (bottomView - topView);
			}

			// Handle cases: 5 & 6 above.
			return topView;
		}

		// Does not support other scrolling than LineLeft/LineRight
		public void MouseWheelDown()
		{
		}

		public void MouseWheelLeft()
		{
		}

		public void MouseWheelRight()
		{
		}

		public void MouseWheelUp()
		{
		}

		public void LineDown()
		{
		}

		public void LineUp()
		{
		}

		public void PageDown()
		{
		}

		public void PageLeft()
		{
		}

		public void PageRight()
		{
		}

		public void PageUp()
		{
		}

		public void SetVerticalOffset(double offset)
		{
		}

		public bool CanVerticallyScroll
		{
			get { return false; }
			set { }
		}

		public bool CanHorizontallyScroll
		{
			get { return true; }
			set { }
		}

		public double ExtentHeight
		{
			get { return 0.0; }
		}

		public double VerticalOffset
		{
			get { return 0.0; }
		}

		public double ViewportHeight
		{
			get { return 0.0; }
		}

		private ScrollData ScrollData
		{
			get
			{
				return _scrollData ?? (_scrollData = new ScrollData());
			}
		}

		private ScrollData _scrollData;

		internal static double ValidateInputOffset(double offset, string parameterName)
		{
			if (double.IsNaN(offset))
			{
				throw new ArgumentOutOfRangeException(parameterName);
			}

			return Math.Max(0.0, offset);
		}
		#endregion
	}
}
